package eu.catengine.renderable.shape;

import eu.catengine.common.iface.IShape;
import eu.catengine.common.listener.ColorListener;
import eu.catengine.common.listener.PositionListener;
import eu.catengine.common.listener.RotationListener;
import eu.catengine.common.listener.ScaleListener;
import eu.catengine.primitive.Color;
import eu.catengine.primitive.Point;
import eu.catengine.primitive.Size;
import eu.catengine.primitive.Vector;
import eu.catengine.renderable.Renderable;

/**
 * Klasa służy jako podstawowy obiekt, który może być wykorzystywany w scenie. <br>
 * Rozszerze klasę Renderable o wiele dodatkowych narzędzi.<br>
 * Chcąc stworzyć własny obiekt najlepiej dziedziczyć tą klasę.
 * 
 * @author mbienkowski
 */
public abstract class Shape extends Renderable implements IShape
{
    protected float mScaleX = 1.0f;
    protected float mScaleY = 1.0f;
    protected Color mColor = new Color();
    protected Size mSize = new Size(1, 1);

    protected ScaleListener mScaleListener;
    protected ColorListener mColorListener;
    protected PositionListener mPositionListener;
    protected RotationListener mRotationListener;

    @Override
    public void moveBy(Vector vector)
    {
	moveBy(vector.x, vector.y);
    }

    @Override
    public void moveBy(float x, float y)
    {
	for (int i = 0; i < mVertices.length; i += 2)
	{
	    mVertices[i] += x;
	    mVertices[i + 1] += y;
	}

	updateBuffer();

	if (mPositionListener != null)
	{
	    mPositionListener.onPositionChanged(this, x, y);
	}
    }

    @Override
    public void moveTo(Point point)
    {
	moveTo(point.x, point.y);
    }

    public void moveTo(float x, float y)
    {
	float diffX = x - mVertices[2];
	float diffY = y - mVertices[3];

	for (int i = 0; i < mVertices.length; i += 2)
	{
	    mVertices[i] += diffX;
	    mVertices[i + 1] += diffY;
	}

	updateBuffer();

	if (mPositionListener != null)
	{
	    mPositionListener.onPositionChanged(this, diffX, diffY);
	}
    }

    @Override
    public void setPositionListener(PositionListener listener)
    {
	mPositionListener = listener;
    }

    @Override
    public Size getSize()
    {
	return mSize;
    }

    @Override
    public float getWidth()
    {
	return mSize.width;
    }

    @Override
    public float getHeight()
    {
	return mSize.height;
    }

    @Override
    public void setActualScaleAsNormal()
    {
	mScaleX = 1.0f;
	mScaleY = 1.0f;
    }

    @Override
    public void scaleBy(float scale)
    {
	scaleByAtPoint(getAnchorPoint().x, getAnchorPoint().y, scale, scale);
    }

    @Override
    public void scaleBy(float scaleX, float scaleY)
    {
	scaleByAtPoint(getAnchorPoint().x, getAnchorPoint().y, scaleX, scaleY);
    }

    @Override
    public void scaleByAtPoint(Point point, float scale)
    {
	scaleByAtPoint(point.x, point.y, scale, scale);
    }

    @Override
    public void scaleByAtPoint(float x, float y, float scale)
    {
	scaleByAtPoint(x, y, scale, scale);
    }

    @Override
    public void scaleByAtPoint(Point point, float scaleX, float scaleY)
    {
	scaleByAtPoint(point.x, point.y, scaleX, scaleY);
    }

    @Override
    public void scaleByAtPoint(float x, float y, float scaleX, float scaleY)
    {
	mScaleX *= scaleX;
	mScaleY *= scaleY;

	mSize.multiply(scaleX, scaleY);

	Vector moveVec1 = new Vector(-x, -y);
	Vector moveVec2 = new Vector(x, y);

	for (int i = 0; i < mVertices.length; i += 2)
	{
	    mVertices[i] += moveVec1.x;
	    mVertices[i + 1] += moveVec1.y;
	}

	for (int i = 0; i < mVertices.length; i += 2)
	{
	    mVertices[i] *= scaleX;
	    mVertices[i + 1] *= scaleY;
	}

	for (int i = 0; i < mVertices.length; i += 2)
	{
	    mVertices[i] += moveVec2.x;
	    mVertices[i + 1] += moveVec2.y;
	}

	this.updateBuffer();

	if (mScaleListener != null)
	{
	    mScaleListener.onScaleChanged(this, scaleX, scaleY);
	}
    }

    @Override
    public void scaleByAtCenter(float scale)
    {
	scaleByAtPoint(getCenterPoint(), scale, scale);
    }

    @Override
    public void scaleByAtCenter(float scaleX, float scaleY)
    {
	scaleByAtPoint(getCenterPoint(), scaleX, scaleY);
    }

    @Override
    public void scaleTo(float scale)
    {
	float tmpX = scale / mScaleX;
	float tmpY = scale / mScaleX;

	scaleByAtPoint(getAnchorPoint().x, getAnchorPoint().y, tmpX, tmpY);
    }

    @Override
    public void scaleTo(float scaleX, float scaleY)
    {
	float tmpX = scaleX / mScaleX;
	float tmpY = scaleY / mScaleX;

	scaleByAtPoint(getAnchorPoint().x, getAnchorPoint().y, tmpX, tmpY);
    }

    @Override
    public void scaleToAtPoint(Point point, float scale)
    {
	float tmpX = scale / mScaleX;
	float tmpY = scale / mScaleX;

	scaleByAtPoint(point.x, point.y, tmpX, tmpY);
    }

    @Override
    public void scaleToAtPoint(float x, float y, float scale)
    {
	float tmpX = scale / mScaleX;
	float tmpY = scale / mScaleX;

	scaleByAtPoint(x, y, tmpX, tmpY);
    }

    @Override
    public void scaleToAtPoint(Point point, float scaleX, float scaleY)
    {
	float tmpX = scaleX / mScaleX;
	float tmpY = scaleY / mScaleX;

	scaleByAtPoint(point.x, point.y, tmpX, tmpY);
    }

    @Override
    public void scaleToAtPoint(float x, float y, float scaleX, float scaleY)
    {
	float tmpX = scaleX / mScaleX;
	float tmpY = scaleY / mScaleX;

	scaleByAtPoint(x, y, tmpX, tmpY);
    }

    @Override
    public void scaleToAtCenter(float scale)
    {
	float tmpX = scale / mScaleX;
	float tmpY = scale / mScaleX;

	Point p = getCenterPoint();

	scaleByAtPoint(p.x, p.y, tmpX, tmpY);
    }

    @Override
    public void scaleToAtCenter(float scaleX, float scaleY)
    {
	float tmpX = scaleX / mScaleX;
	float tmpY = scaleY / mScaleX;

	Point p = getCenterPoint();

	scaleByAtPoint(p.x, p.y, tmpX, tmpY);
    }

    @Override
    public void setScaleListener(ScaleListener listener)
    {
	mScaleListener = listener;
    }

    @Override
    public float getScaleX()
    {
	return mScaleX;
    }

    @Override
    public float getScaleY()
    {
	return mScaleY;
    }

    @Override
    public void setColor(Color color)
    {
	this.mColor = color;

	if (mColorListener != null)
	{
	    mColorListener.onColorChanged(this);
	}
    }

    @Override
    public void setColor(float red, float green, float blue)
    {
	mColor.red = red;
	mColor.green = green;
	mColor.blue = blue;

	if (mColorListener != null)
	{
	    mColorListener.onColorChanged(this);
	}
    }

    @Override
    public void setColor(float red, float green, float blue, float alpha)
    {
	mColor.red = red;
	mColor.green = green;
	mColor.blue = blue;
	mColor.alpha = alpha;

	if (mColorListener != null)
	{
	    mColorListener.onColorChanged(this);
	}
    }

    @Override
    public Color getColor()
    {
	return mColor;
    }

    @Override
    public float getRed()
    {
	return mColor.red;
    }

    @Override
    public float getGreen()
    {
	return mColor.green;
    }

    @Override
    public float getBlue()
    {
	return mColor.blue;
    }

    @Override
    public float getAlpha()
    {
	return mColor.alpha;
    }

    @Override
    public void setColorListener(ColorListener listener)
    {
	mColorListener = listener;
    }

    @Override
    public void rotateBy(float angle)
    {
	rotateByAtPoint(getAnchorPoint().x, getAnchorPoint().y, angle);
    }

    @Override
    public void rotateByAtPoint(Point rotatePoint, float angle)
    {
	rotateByAtPoint(rotatePoint.x, rotatePoint.y, angle);
    }

    @Override
    public void rotateByAtPoint(float x, float y, float angle)
    {
	angle *= Math.PI / 180.0f;

	for (int i = 0; i < mVertices.length; i += 2)
	{
	    float tmp = (float) (Math.cos(angle) * (mVertices[i] - x) - Math.sin(angle) * (mVertices[i + 1] - y) + x);
	    mVertices[i + 1] = (float) (Math.sin(angle) * (mVertices[i] - x) + Math.cos(angle) * (mVertices[i + 1] - y) + y);
	    mVertices[i] = tmp;
	}

	if (mRotationListener != null)
	{
	    mRotationListener.onRotationChanged(this, angle);
	}

	updateBuffer();
	;
    }

    @Override
    public void rotateByAtCenter(float angle)
    {
	rotateByAtPoint(this.getCenterPoint(), angle);
    }

    @Override
    public void setRotationListener(RotationListener listener)
    {
	mRotationListener = listener;
    }

    @Override
    public void dispose()
    {
	mColor = null;
	mSize = null;
	mScaleListener = null;
	mColorListener = null;
	mPositionListener = null;
	mRotationListener = null;

	super.dispose();
    }

}
